//=============================================================================
// System  : MatchKit
// File    : MatchableMemberInfo.cs
// Author  : Fabio Bussu  (matchkit@fabiobussu.it)
// Updated : 04/16/2013
// Note    : Copyright 2013, Fabio Bussu, All rights reserved
//
// This code is published under the Microsoft Public License (Ms-PL). A copy
// of the license should be distributed with the code.  It can also be found
// at the project website: http://matchkit.codeplex.com. This notice, the
// author's name, and all copyright notices must remain intact in all
// applications, documentation, and source files.
//
// Version     Date     Who  Comments
// ============================================================================
// 1.0.0.0  04/16/2013   FB  First draft
//=============================================================================
using System;
using System.Collections.Generic;
using System.Reflection;
using MatchKit.ObjectMapping.Attributes;
using MatchKit.ObjectMapping.Patterns;
using MatchKit.Patterns;

namespace MatchKit.ObjectMapping
{
	/// <summary>
	/// Static information for a matchable member
	/// </summary>
	sealed class MatchableMemberInfo
	{
		public readonly FieldOrProperty RawMember;
		public readonly Type Type;
		public readonly bool IsOptional;
		public readonly bool IsReadOnly;
		public readonly bool IsArray;
		public readonly bool IsDynamic;
		public readonly bool IsMatchableType;
		public readonly bool IsFinalIfNotfound;			// solo con IsOptional
		public readonly bool IsPointOfNoReturn;
		public readonly bool IsSeparatorElement;
		public readonly GetPatternsMethodHandler GetPatternsMethod;
		private readonly IPattern _pattern;
		public readonly bool IsNumber;
		public readonly bool IsString;
		public readonly bool IsTypeIPattern;
		public readonly bool IsTypeMatch;
		public readonly bool SuspendBlankIgnoringAfterMember;
		private readonly bool _isConst;
		private readonly object _constMemberValue;

		public MatchableMemberInfo(FieldOrProperty member)
		{
			if (!member.CanRead)
				throw new MatchKitMappingException(6, "The matchable property [{0}] must have the get accessor"
					, member.FullName);
			if (member.IsIndexed)
				throw new MatchKitMappingException(7, "The matchable property [{0}] cannot be an indexer"
					, member.FullName);
			if (member.IsStatic && !member.IsConst)
				throw new MatchKitMappingException(8, "The matchable property [{0}] cannot be static and not readonly"
					, member.FullName);

			/*
			 * i membri "static readonly" oppure "const" non richiedono IsDynamic=true per interpretare il valore iniziale staticamente;
			 * i membri static sono ammessi solo readonly;
			 * i field, sia readonly che non, per interpretare il valore devono essere IsDynamic=true;
			 * le property static quindi non sono ammesse;
			 * le property devono essere sempre IsDynamic=true per interpretare il valore;
			 */

			RawMember = member;
			Type = member.Type;

			if (Type.IsArray)
			{
				if (Type.GetArrayRank() > 1)
					throw new MatchKitMappingException(9, "The matchable member [{0}] cannot be a multi-dimensional array"
						, RawMember.FullName);
				Type = Type.GetElementType();
				IsArray = true;
			}

			IsMatchableType = ReflectionUtils.IsMatchableClass(Type);

			var matchableMemberAttribute = member.GetCustomAttribute<MatchableMemberAttribute>();
			IsFinalIfNotfound = matchableMemberAttribute.IsFinalIfNotFound;
			IsDynamic = matchableMemberAttribute.IsDynamic;
			IsPointOfNoReturn = matchableMemberAttribute.IsPointOfNoReturn;
			IsSeparatorElement = matchableMemberAttribute.IsSeparatorElement;
			IsOptional = matchableMemberAttribute.IsOptional || IsSeparatorElement || IsFinalIfNotfound;
			GetPatternsMethod = GetGetPatternsMethodDelegate(matchableMemberAttribute.GetPatternsMethodType, matchableMemberAttribute.GetPatternsMethod);
			IsReadOnly = RawMember.IsReadOnly;
			SuspendBlankIgnoringAfterMember = matchableMemberAttribute.SuspendBlankIgnoringAfterMember;
			IsTypeIPattern = typeof(IPattern).IsAssignableFrom(Type);
			IsTypeMatch = typeof(Match).IsAssignableFrom(Type);
			IsNumber = ReflectionUtils.IsNumber(Type);
			IsString = Type == typeof(string);
			_isConst = RawMember.IsConst;
			if (_isConst)
				_constMemberValue = RawMember.GetValue(null);

			if (IsOptional && IsPointOfNoReturn)
				throw new MatchKitMappingException(10, "The matchable member [{0}] cannot be both point-of-no-return and optional"
					, RawMember.FullName);

			if (IsArray && IsPointOfNoReturn)
				throw new MatchKitMappingException(11, "The matchable member [{0}] cannot be both point-of-no-return and repeatable"
					, RawMember.FullName);

			if (IsArray && IsDynamic)
				throw new MatchKitMappingException(12, "The matchable member [{0}] cannot be dynamic because it is an array"
					, RawMember.FullName);

			if (IsArray && IsTypeMatch)
				throw new MatchKitMappingException(21, "The matchable member [{0}] cannot be both array and IMatch"
					, RawMember.FullName);

			if (IsArray && IsTypeIPattern)
				throw new MatchKitMappingException(22, "The matchable member [{0}] cannot be both array and IPattern inherited"
					, RawMember.FullName);

			// se e' dinamico rimando tutto al momento della CreatePattern, anche
			// per l'eventual GetPatternMethod
			/*
			 * il Dynamic permette di valutare il tipo di pattern a runtime in base al valore del member;
			 * quindi e' possibile specificare un member con un Type non valido, e a runtime far trovare
			 * valorizzato il member con un'istanza di un tipo valido;
			 * l'istanza trovata a runtime viene anche utilizzata per scrivere i valori trovati
			 */
			if (IsDynamic)
				return;

			_pattern = PrivateCreatePattern(Type, _constMemberValue, IsMatchableType);
		}

		private List<IPattern> GetExplicitPatterns(object memberValue)
		{
			var patterns = new List<IPattern>();

			var attributes = LinqUtils.ToList(RawMember.GetCustomAttributes<PatternBaseAttribute>());
			attributes.Sort((a, b) => a.Position.CompareTo(b.Position));

			// prendo gli eventuali attributi con i pattern possibili
			foreach (var attr in attributes)
			{
				var pba = attr as PatternBaseAttribute;
				if (pba != null)
				{
					var p = pba.CreatePattern(memberValue, IsArray);

					if (p == null)
						throw new MatchKitMappingException(13, "Attribute [{0}] for the matchable member [{1}.{2}] cannot return a null pattern"
							, attr.GetType().FullName
							, RawMember.DeclaringType.FullName, RawMember.Name);

					patterns.Add(p);
				}
			}

			// se e' specificato il metodo GetPatternMethod
			if (GetPatternsMethod != null)
			{
				var tempPatterns = new List<IPattern>();

				GetPatternsMethod(memberValue, RawMember.Member, tempPatterns);

				foreach (var p in tempPatterns)
				{
					if (p == null)
						throw new MatchKitMappingException(14, "GetPatternMethod [{0}] for the matchable member [{1}.{2}] cannot return a null pattern"
							, GetPatternsMethod.Method.Name
							, RawMember.DeclaringType.FullName, RawMember.Name);

					patterns.Add(p);
				}
			}

			return patterns;
		}

		private GetPatternsMethodHandler GetGetPatternsMethodDelegate(Type methodType, string methodName)
		{
			if (string.IsNullOrEmpty(methodName))
				return null;
			var type = RawMember.DeclaringType;
			if (methodType != null)
				type = methodType;
			var method = type.GetMethod(methodName, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static);
			if (method == null)
				throw new MatchKitMappingException(15, "The matchable member [{0}.{1}] specifies a missing GetPatternMethod"
					, RawMember.DeclaringType.FullName, RawMember.Name);
			try
			{
				return (GetPatternsMethodHandler)GetPatternsMethodHandler.CreateDelegate(typeof(GetPatternsMethodHandler), method);
			}
			catch (Exception ex)
			{
				throw new MatchKitMappingException(16, string.Format("The matchable member [{0}.{1}] specify an invalid GetPatternMethod"
					, RawMember.DeclaringType.FullName, RawMember.Name), ex);
			}
		}

		// il terzo parametro c'e' perche' nel caso d IsDynamic=true il flag della classe potrebbe non corispondere
		// al vero dal momento che il tipo e' dinamico
		private IPattern ResolveTypeMapping(Type type, object memberValue, bool isMatchableType)
		{
			// calcolo la chiave per la lookup del default mappings
			var registeringTypeKey = type;

			if (isMatchableType)
				registeringTypeKey = DefaultTypeMappings.MatchableClass;
			else if (IsTypeIPattern)
				registeringTypeKey = typeof(IPattern);

			// verifico se riesco a risolvere il Type per
			if (DefaultTypeMappings.IsRegistered(registeringTypeKey))
			{
				var p = DefaultTypeMappings.Map(registeringTypeKey, type, memberValue, IsArray);
				if (p == null)
					throw new MatchKitMappingException(17, "The mapping for type [{0}] on the member [{1}] cannot return null pattern"
						, Type.FullName, RawMember.FullName);
				return p;
			}
			else
			{
				return null;
			}
		}

		private IPattern PrivateCreatePattern(Type type, object memberValue, bool isMatchableType)
		{
			// recupero i pattern specificati
			var patterns = GetExplicitPatterns(memberValue);

			// solo se non ci sono pattern specificati esplicitamente
			if (patterns.Count == 0)
			{
				var resolved = ResolveTypeMapping(type, memberValue, isMatchableType);
				if (resolved != null)
					patterns.Add(resolved);
			}

			// nessun pattern specificato o derivabile dal Type o dai default mapings
			if (patterns.Count == 0)
				throw new MatchKitMappingException(18, "The matchable member [{0}] must specify at least one PatternBaseAttribute, declaring a valid type or register a default mapping"
					, RawMember.FullName);

			IPattern pattern = null;

			if (patterns.Count == 1)
				pattern = patterns[0];
			else
				pattern = new InternalExclusivePattern(patterns.ToArray());

			if (IsArray)
				pattern = new InternalRepeaterPattern(pattern);

			if (IsOptional)
				pattern = new OptionalPattern(pattern);

			pattern.IsPointOfNoReturn = IsPointOfNoReturn;

			return pattern;
		}

		public IPattern CreatePattern(object instance)
		{
			if (IsDynamic)
			{
				if (instance == null)
					throw new MatchKitMappingException(19, "Cannot create pattern dynamically from null instance for member [" + RawMember.FullName + "]");

				var memberValue = RawMember.GetValue(instance);
				var type = memberValue == null ? Type : memberValue.GetType();
				var isMatchable = ReflectionUtils.IsMatchableClass(type);

				return PrivateCreatePattern(type, memberValue, isMatchable);
			}
			else
			{
				return _pattern;
			}
		}
	}
}
